home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / sbin / update-python-modules < prev    next >
Text File  |  2008-06-30  |  14KB  |  385 lines

  1. #! /usr/bin/python
  2. #
  3. # copyright (c) 2006 Josselin Mouette <joss@debian.org>
  4. # Licensed under the GNU Lesser General Public License, version 2.1
  5. # See COPYING for details
  6.  
  7. import sys,os,shutil
  8. from optparse import OptionParser
  9. from py_compile import compile, PyCompileError
  10. sys.path.append("/usr/lib/python-support/private/")
  11. import pysupport
  12. from pysupport import py_supported,py_installed,py_oldversions
  13.  
  14. basepath='/var/lib/python-support'
  15. sourcepath='/usr/share/python-support'
  16. extensionpath='/usr/lib/python-support'
  17.  
  18. parser = OptionParser(usage="usage: %prog [-v] [-c] package_directory [...]\n"+
  19.                             "       %prog [-v] [-c] package.dirs [...]\n"+
  20.                             "       %prog [-v] [-a|-f|-p]")
  21. parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  22.                   help="verbose output", default=False)
  23. parser.add_option("-c", "--clean", action="store_true", dest="clean_mode",
  24.                   help="clean modules instead of compiling them",
  25.                   default=False)
  26. parser.add_option("-a", "--rebuild-all", action="store_true",
  27.                   dest="rebuild_all", default=False,
  28.                   help="rebuild all private modules for a new default python version")
  29. parser.add_option("-f", "--force-rebuild-all", action="store_true",
  30.                   dest="rebuild_everything", default=False,
  31.                   help="rebuild all modules, including public modules for all python versions")
  32. parser.add_option("-p", "--post-install", action="store_true", dest="post_install",
  33.                   help="run post-installation operations, common to many packages",
  34.                   default=False)
  35. parser.add_option("-b", "--bytecompile", action="store_true", dest="force_private",
  36.                   help="[deprecated] byte-compilation mode: only handle private modules",
  37.                   default=False)
  38. parser.add_option("-i", "--install", action="store_true", dest="force_public",
  39.                   help="[deprecated] installation mode: only handle public modules",
  40.                   default=False)
  41. (options, args) = parser.parse_args()
  42.  
  43. def debug(x):
  44.   if(options.verbose):
  45.     print x
  46.  
  47. # I should use the sets type instead
  48. def isect(l1,l2):
  49.   return [i for i in l1 if i in l2]
  50.   
  51. def concat(l1,l2):
  52.   return l1 + [i for i in l2 if i not in l1]
  53.  
  54. versions_dict={}
  55.  
  56. def dir_versions(dir):
  57.   if dir not in versions_dict:
  58.     verfile=os.path.join(dir,'.version')
  59.     if dir.startswith(extensionpath):
  60.       # Directory in /usr/lib: only one version
  61.       vers=os.path.split(dir)[1]
  62.       if vers in py_supported:
  63.         versions_dict[dir]=[vers]
  64.       else:
  65.         versions_dict[dir]=[]
  66.     elif dir.startswith(sourcepath):
  67.       # Directory in /usr/share
  68.       extdir=dir.replace(sourcepath,extensionpath,1)
  69.       if os.path.exists(verfile):
  70.         # If we have a .version, use it
  71.         versions_dict[dir]=pysupport.version_list(file(verfile).readline())
  72.       elif os.path.isdir(extdir):
  73.         # Try to obtain the list of supported versions
  74.         # from the extensions in /usr/lib
  75.         versions_dict[dir]=isect(py_supported,os.listdir(extdir))
  76.       else:
  77.         # Otherwise, support all versions
  78.         versions_dict[dir]=py_supported
  79.     else:
  80.       raise "[Internal error] %s: unsupported path for byte-compilation."
  81.   return versions_dict[dir]
  82.  
  83. def bytecompile_only(basedir,dir,file):
  84.   if file.endswith('.py'):
  85.     fullpath=os.path.join(basedir,dir,file)
  86.     debug("compile "+fullpath+'c')
  87.     try:
  88.       # Note that compile doesn't raise PyCompileError by default
  89.       compile(fullpath, doraise=True)
  90.     except IOError, (errno, strerror):
  91.       sys.stderr.write("WARNING: I/O error while trying to byte-compile %s (%s): %s\n" % (fullpath, errno, strerror))
  92.     except PyCompileError, inst:
  93.       sys.stderr.write("WARNING: compile error while trying to byte-compile %s: %s\n" % (fullpath, inst.msg))
  94.     except:
  95.       sys.stderr.write("WARNING: unexpected error while trying to byte-compile %s: %s\n" % (fullpath, sys.exc_info()[0]))
  96.  
  97. def clean_simple(basedir,dir,file):
  98.   if file.endswith('.py'):
  99.     for ext in ['c','o']:
  100.       fullpath=os.path.join(basedir,dir,file+ext)
  101.       if os.path.exists(fullpath):
  102.         debug("remove "+fullpath)
  103.         os.remove(fullpath)
  104.  
  105. def install_modules(versions):
  106.   def install_modules_func(basedir,dir,file):
  107.     if file == '.version':
  108.       return
  109.     fullpath=os.path.join(basedir,dir,file)
  110.     for py in isect(dir_versions(basedir),versions):
  111.       destpath=os.path.join(basepath,py,dir,file)
  112.       try:
  113.         os.makedirs(os.path.join(basepath,py,dir))
  114.       except OSError:
  115.         pass
  116.       if file[-4:] not in ['.pyc','.pyo']:
  117.         debug("link "+destpath)
  118.         # os.path.exists returns False for broken symbolic links
  119.         if os.path.exists(destpath) or os.path.islink(destpath):
  120.           if file!="__init__.py" or (os.path.exists(destpath) and os.path.getsize(destpath)):
  121.             # Oops, the file already exists and is not empty.
  122.             # Check whether we are conflicting with something else.  
  123.             for otherdir in dirs_i:
  124.               otherextdir = os.path.join(otherdir.replace(sourcepath,extensionpath,1),py)
  125.               if basedir in [otherdir,otherextdir]:
  126.                 continue
  127.               if os.path.exists(os.path.join(otherdir,dir,file)) or os.path.exists(os.path.join(otherextdir,dir,file)):
  128.                 parser.error("Trying to overwrite %s which is already provided by %s"%(os.path.join(dir,file),otherdir))
  129.             # The file is already here, probably from the previous version. 
  130.             # Let's proceed.
  131.             debug("overwrite "+destpath)
  132.           else:
  133.             debug("overwrite namespace "+destpath)
  134.           os.remove(destpath)
  135.         os.symlink(fullpath,destpath)
  136.       # Files are NOT byte-compiled here, this will be done later.
  137.   return install_modules_func
  138.  
  139. def process(basedir,func):
  140.   debug("Looking at %s..."%(basedir))
  141.   for dir, dirs, files in os.walk(basedir):
  142.     dir = dir[len(basedir):].lstrip('/')
  143.     for file in files:
  144.       func(basedir, dir, file)
  145.     for file in dirs:
  146.       if os.path.islink(os.path.join(basedir,dir,file)):
  147.         func(basedir, dir, file)
  148.  
  149. def process_extensions(basedir,func,version=None):
  150.   basedir=basedir.replace(sourcepath,extensionpath,1)
  151.   if os.path.isdir(basedir):
  152.     for vers in os.listdir(basedir):
  153.       if version and vers != version:
  154.         continue
  155.       verdir=os.path.join(basedir,vers)
  156.       if os.path.isdir(verdir):
  157.         process(verdir,func([vers]))
  158.  
  159. def dirlist_file(f):
  160.   return [ l.rstrip('\n') for l in file(f) if len(l)>1 ]
  161.  
  162. def bytecompile_all(py,path=None):
  163.   if not path:
  164.     path=os.path.join(basepath,py)
  165.   if not os.path.isdir(path):
  166.     return
  167.   debug("Byte-compilation of whole %s..."%path)
  168.   os.spawnl(os.P_WAIT, '/usr/bin/'+py, py,
  169.             os.path.join('/usr/lib/',py,'compileall.py'), '-q', path)
  170.  
  171. def bytecompile_privatedir(basedir):
  172.   versionfile=os.path.join(basedir,".pyversion")
  173.   if os.path.isfile(versionfile):
  174.     specific_version=file(versionfile).readline().rstrip('\n')
  175.     bytecompile_all("python"+specific_version,basedir)
  176.   else:
  177.     process(basedir,bytecompile_only)
  178.  
  179. def create_dotpath(py,dirhash=None):
  180.   path=os.path.join(basepath,py)
  181.   pathfile=os.path.join(path,".path")
  182.   debug("Generation of %s..."%pathfile)
  183.   pathlist=[path]
  184.   for f in os.listdir(path):
  185.     f=os.path.join(path,f)
  186.     if f.endswith(".pth") and os.path.isfile(f):
  187.       for l in file(f):
  188.         l=l.rstrip('\n')
  189.         pathlist.append(l)
  190.         l2=os.path.join(path,l)
  191.         pathlist.append(l2)
  192.         if dirhash:
  193.           dirhash[l2]=False
  194.   fd=file(pathfile,"w")
  195.   fd.writelines([l+'\n' for l in pathlist])
  196.   fd.close()
  197.  
  198. def post_change_stuff(py):
  199.   # All the changes that need to be done after anything has changed
  200.   # in a /var/lib/python-support/pythonX.Y directory
  201.   # * Cleanup of all dangling symlinks that are left out after a package
  202.   #   is upgraded/removed.
  203.   # * The namespace packages are here because python doesn't consider a
  204.   #   directory to be able to contain packages if there is no __init__.py
  205.   #   file (yes, this is completely stupid).
  206.   # * The .path file must be created by concatenating all those .pth
  207.   #   files that extend sys.path (this also badly sucks).
  208.   # * Byte-compilation of all .py files that haven't already been
  209.   path=os.path.join(basepath,py)
  210.   if not os.path.isdir(path):
  211.     return
  212.   # First, remove any dangling symlinks.
  213.   # In the same loop, we find which directories may need a namespace package
  214.   dirhash={}
  215.   for dir, dirs, files in os.walk(path):
  216.     dirhash[dir]=False
  217.     files.sort() # We need the .py to appear before the .pyc
  218.     for f in files+dirs:
  219.       # We also examine dirs as some symlinks are dirs
  220.       abspath=os.path.join(dir,f)
  221.       islink=os.path.islink(abspath)
  222.       if islink:
  223.         if not os.path.exists(abspath):
  224.           # We refer to a file that was removed
  225.           debug("remove "+abspath)
  226.           os.remove(abspath)
  227.           continue
  228.         srcfile = os.readlink (abspath)
  229.         # Remove links left here after a change in the supported python versions for a package
  230.         if srcfile.startswith(sourcepath):
  231.           if py not in dir_versions (os.path.join(sourcepath,srcfile[len(sourcepath)+1:].split("/",1)[0])):
  232.             debug("remove "+abspath)
  233.             os.remove(abspath)
  234.             continue
  235.       if f[-4:] in ['.pyc', '.pyo']:
  236.         if not os.path.exists(abspath[:-1]):
  237.           debug("remove "+abspath)
  238.           os.remove(abspath)
  239.           continue
  240.       elif f[-3:] in ['.py', '.so']:
  241.         if islink or f!='__init__.py':
  242.           # List the directory as maybe needing a namespace packages
  243.           d=dir
  244.           while dirhash.has_key(d) and not dirhash[d]:
  245.             dirhash[d]=True
  246.             d=os.path.dirname(d)
  247.     # Remove the directory if it is empty after our crazy removals
  248.     try:
  249.       os.removedirs(dir)
  250.     except OSError:
  251.       pass
  252.   dirhash[path]=False
  253.   # Then, find which directories belong in a .pth file
  254.   # These directories don't need a namespace package, so we
  255.   # pass the dirhash
  256.   create_dotpath(py,dirhash)
  257.   # Finally, create/remove namespace packages
  258.   for dir in dirhash:
  259.     initfile=os.path.join(dir,"__init__.py")
  260.     if dirhash[dir]:
  261.       if not os.path.exists(initfile):
  262.         debug("create namespace "+initfile)
  263.         file(initfile,"w").close()
  264.     else:
  265.       for e in ['','c','o']:
  266.         if os.path.exists(initfile+e):
  267.           debug('remove namespace '+initfile+e)
  268.           os.remove(initfile+e)
  269.       try:
  270.         os.removedirs(dir)
  271.       except OSError:
  272.         pass
  273.   bytecompile_all(py)
  274.  
  275. # Parse arguments
  276. do_dirs_i=[]
  277. do_dirs_b=[]
  278. for arg in args:
  279.   if os.path.isabs(arg):
  280.     if not arg.startswith(sourcepath):
  281.       parser.error("%s is not in the python-support directory."%arg)
  282.   else:
  283.     arg=os.path.join(sourcepath,arg)
  284.   if not os.path.exists(arg):
  285.     if options.clean_mode:
  286.       sys.stderr.write("WARNING: %s does not exist.\n         Some bytecompiled files may be left behind.\n"%arg)
  287.       continue
  288.     else:
  289.       parser.error("%s does not exist"%arg)
  290.   if arg.endswith('.dirs'):
  291.     do_dirs_b+=dirlist_file(arg)
  292.     if options.force_public:
  293.       parser.error("Option -i cannot be used with a private module .dirs file.")
  294.   elif os.path.isdir(arg):
  295.     do_dirs_i.append(arg)
  296.     if options.force_private:
  297.       parser.error("Option -b cannot be used with a public module directory.")
  298.   else:
  299.     parser.error("%s is not a directory"%arg)
  300.  
  301.  
  302. # Read full list from the source directory
  303. # directories are stuff to be installed
  304. # foo.dirs files list directories to bytecompile in place
  305. dirs_b = []
  306. dirs_i = []
  307. for f in os.listdir(sourcepath):
  308.   f=os.path.join(sourcepath,f)
  309.   if os.path.isdir(f):
  310.     dirs_i.append(f)
  311.   elif f.endswith('.dirs'):
  312.     dirs_b+=dirlist_file(f)
  313.  
  314. if not os.path.isdir(basepath):
  315.   os.mkdir(basepath)
  316.  
  317. if options.rebuild_everything:
  318.   options.rebuild_all = True
  319.   for pyver in py_supported:
  320.     dir = os.path.join(basepath,pyver)
  321.     if os.path.isdir(dir):
  322.       shutil.rmtree(dir)
  323.  
  324. # Check for changes in installed python versions
  325. for pyver in py_oldversions+py_supported:
  326.   dir = os.path.join(basepath,pyver)
  327.   # Check for ".path" because sometimes the directory already exists 
  328.   # while the python version isn't installed, because of some .so's.
  329.   if pyver in py_installed and not os.path.isfile(os.path.join(dir,".path")):
  330.     debug("Building all modules in %s..."%(dir))
  331.     for basedir in dirs_i:
  332.       process(basedir,install_modules([pyver]))
  333.       process_extensions(basedir,install_modules,pyver)
  334.     # Here we need to launch post_change_stuff because otherwise we could
  335.     # end up without the .path file that is checked 6 lines earlier
  336.     post_change_stuff (pyver)
  337.   if pyver not in py_installed and os.path.isdir(dir):
  338.     debug("Removing obsolete directory %s..."%(dir))
  339.     shutil.rmtree(dir)
  340.  
  341. if options.rebuild_all:
  342.   for basedir in dirs_b:
  343.     process(basedir,clean_simple)
  344.     bytecompile_privatedir(basedir)
  345.  
  346.  
  347. # Now for the processing of what was handed on the command line
  348. for basedir in do_dirs_b:
  349.   if not options.clean_mode:
  350.     bytecompile_privatedir(basedir)
  351.   else:
  352.     process(basedir,clean_simple)
  353.  
  354. need_dotpath = False
  355. need_postinstall = []
  356. for basedir in do_dirs_i:
  357.   need_postinstall = concat(need_postinstall,isect(dir_versions(basedir),py_installed))
  358.   if not options.clean_mode:
  359.     process(basedir,install_modules(py_installed))
  360.     process_extensions(basedir,install_modules)
  361.     for f in os.listdir(basedir):
  362.       if f.endswith(".pth"):
  363.         need_dotpath = True
  364.  
  365. # Only do the funny and time-consuming things when the -p option is
  366. # given, e.g when python-support is triggered.
  367. if need_postinstall and 'DPKG_RUNNING_VERSION' in os.environ and not options.post_install:
  368.   ret = os.spawnlp(os.P_WAIT, 'dpkg-trigger', 'dpkg-trigger', '--no-await', 'pysupport')
  369.   if ret:
  370.     sys.stderr.write("ERROR: dpkg-trigger failed\n")
  371.     sys.exit(1)
  372.   need_postinstall = []
  373.  
  374. if options.post_install:
  375.   # Now the trigger is activated, do it for all installed versions
  376.   need_postinstall = py_installed
  377. if need_postinstall:
  378.   need_dotpath = False
  379.   for py in need_postinstall:
  380.     post_change_stuff(py)
  381.  
  382. if need_dotpath:
  383.   for py in need_postinstall:
  384.     create_dotpath (py)
  385.